package com.szsh.aiot.hsm.service.impl;

import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import com.szsh.aiot.common.core.domain.AjaxResult;
import com.szsh.aiot.common.utils.TimeTools;
import com.szsh.aiot.hsm.dao.MkDao;
import com.szsh.aiot.hsm.domain.*;
import com.szsh.aiot.hsm.domain.device.*;
import com.szsh.aiot.hsm.domain.deviceonline.DeviceOnlineModel;
import com.szsh.aiot.hsm.domain.homeNetWork.NetTopInfo;
import com.szsh.aiot.hsm.domain.netprotect.INetprotectSwitch;
import com.szsh.aiot.hsm.domain.reportdata.ReportModelData;
import com.szsh.aiot.hsm.domain.reportdata.ReportRedisData;
import com.szsh.aiot.hsm.dto.ability.GetApInfo;
import com.szsh.aiot.hsm.dto.ability.GetWifiInfoResponse;
import com.szsh.aiot.hsm.dto.ability.GwInfo;
import com.szsh.aiot.hsm.dto.ability.NetTopologicalInfo;
import com.szsh.aiot.hsm.dto.enu.NumberEnum;
import com.szsh.aiot.hsm.dto.enu.RedisKeyPrefix;
import com.szsh.aiot.hsm.enums.FlagType;
import com.szsh.aiot.hsm.enums.NameSource;
import com.szsh.aiot.hsm.mapper.*;
import com.szsh.aiot.hsm.service.TerminalService;
import com.szsh.aiot.hsm.thirdService.AbilityClient;
import com.szsh.aiot.hsm.utils.password.PasswordTool;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.management.RuntimeErrorException;
import java.security.SecureRandom;
import java.time.DayOfWeek;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import static com.szsh.aiot.hsm.enums.DeviceType.getDeviceType;

/**
 * @Description:
 * @Date 2023/10/25: 9:21
 * @Author: LOADING>>>>>
 */

@Service
public class TerminalServiceImpl implements TerminalService {

    private static final Logger LOGGER = LoggerFactory.getLogger(TerminalServiceImpl.class);
    private static final AtomicInteger ID_TAIL = new AtomicInteger(0);
    private static final DateTimeFormatter DTF = DateTimeFormatter.ofPattern("yyyyMMdd");

    private final Gson gson = new Gson();

    @Autowired
    private IUserCheckRecordMapper checkRecordMapper;

    @Autowired
    private IUserDeviceMapper userDeviceMapper;

    @Autowired
    private AbilityClient abilityClient;

    @Autowired
    private INetprotectSwitchMapper switchMapper;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private IDeviceGoonlineMapper goonlineMapper;

    @Autowired
    private IFlowDeviceMapper flowDeviceMapper;

    @Autowired
    private IUserDeviceMapper deviceMapper;

    @Autowired
    private IDeviceIconMapper iconMapper;

    @Autowired
    private IScanStatMapper statMapper;

    @Autowired
    private MkDao mkReport;

    /**
     * 请求扫描设备
     *
     * @param gatewayMac  网关设备的MAC地址
     * @param broadbandNO 宽带账号
     * @return 下发扫描成功返回scanID，失败返回空字符串
     */
    @Override
    public AjaxResult requireScan(String gatewayMac, String broadbandNO) {

        // 扫描ID
        String scanID = generateScanId("rapidscan");
        // 记录用户扫描请求
        saveCheckRecord(scanID, broadbandNO);
        // 异步查询在线设备
        getDeviceByTopol(broadbandNO, gatewayMac, scanID);

        return AjaxResult.success("扫描完成",scanID);
    }

    /**
     * 是否扫描结束
     *
     * @param scanID
     * @return
     */
    @Override
    public AjaxResult isScanning(String scanID) {
        IUserCheckRecord checkRecord = checkRecordMapper.selectIUserCheckRecordByScanID(scanID);
        if (checkRecord == null) {
            return AjaxResult.success(false);
        }
        // 拓扑接口查询中，都判别为正在检测中
        return AjaxResult.success(NumberEnum.NUM_INT_2.longValue() != checkRecord.getTopolState());
    }

    /**
     * 检索出宽带账号下所有的<b>在线</b>设备列表
     *
     * @param broadbandNO 宽带账号
     * @return 设备列表
     */
    @Override
    public AjaxResult queryOnlineDevice(String broadbandNO) {
        List<DeviceOnlineModel> modelList = new ArrayList<>();
        // 查询用户在线设备
        IUserDevice iUserDevice = new IUserDevice();
        iUserDevice.setBroadbandNO(broadbandNO);
        List<IUserDevice> onlineDevices = userDeviceMapper.selectIUserDeviceBybroadbandNO(broadbandNO);
        onlineDevices.forEach(device -> {
            DeviceOnlineModel model = new DeviceOnlineModel();
            model.setDeviceName(device.getShowName());
            model.setDeviceMac(device.getDeviceMac());
            modelList.add(model);
        });
        return AjaxResult.success(modelList);
    }

    /**
     * 接口请求校验网关设备的密码强度，如果为弱密码则写入设备漏洞记录表中，
     * 前端页面查询时则可以通过此记录来做相应的提示与展示。
     *
     * @param broadbandNO 用户宽带账号
     * @param gatewayMac  网关设备的MAC地址
     * @param scanID      扫描ID
     */
    @Override
    public AjaxResult checkGatewayPwdStrength(String broadbandNO, String gatewayMac, String scanID) {
        int level = 1;
        IUserDevice gatewayDevice = userDeviceMapper.selectIUserDeviceByMac(broadbandNO, gatewayMac);
        if (gatewayDevice == null) {
            return AjaxResult.success(level);
        }

        Long deviceID = gatewayDevice.getDeviceID();
        try {
            // 先清除Redis中网关漏洞信息
            LOGGER.info("根据设备ID[{}]删除[{}]信息", deviceID, "网关弱密码");
            redisTemplate.opsForHash().delete(RedisKeyPrefix.DEVICE_LEAK + deviceID, broadbandNO);

            IUserDevice iUserDevice = new IUserDevice();
            iUserDevice.setDeviceID(deviceID);
            iUserDevice.setScanStatus(NumberEnum.NUM_INT_0);
            userDeviceMapper.updateIUserDevice(iUserDevice);
            GetWifiInfoResponse wifiInfo = abilityClient.getSSIDInfo(gatewayMac);
            if (null == wifiInfo) {
                return AjaxResult.success(1);
            }

            level = PasswordTool.decryptPwdAndEvaluateStrength(wifiInfo.getPwd(),
                    wifiInfo.getLoid());
            if (level >= 3) {
                // 网关弱密码加入到Redis漏洞信息里增加漏洞信息
                updateLeakInfos(gatewayDevice);
                // 如果为弱密码，发送至周报
                reportData(broadbandNO, gatewayDevice);
            }
        } catch (Exception e) {
            level = -1;
            LOGGER.error("[{}]网关弱密码检测失败！", gatewayMac, e);
        } finally {
            int weakPwd = level >= 3 ? 1 : 0;

            IUserDevice iUserDevice = new IUserDevice();
            iUserDevice.setScanStatus(NumberEnum.NUM_INT_2);
            iUserDevice.setDeviceID(deviceID);
            userDeviceMapper.updateIUserDevice(iUserDevice);

            IUserCheckRecord iUserCheckRecord = new IUserCheckRecord();
            iUserCheckRecord.setScanID(scanID);
            iUserCheckRecord.setCheckPwdState(Long.valueOf(NumberEnum.NUM_INT_2));
            iUserCheckRecord.setGateWeakPwd((long) weakPwd);
            checkRecordMapper.updateIUserCheckRecord(iUserCheckRecord);
        }
        return AjaxResult.success(level);
    }


    /**
     * 检索出宽带账号下所有的具有安全风险的<b>在线</b>设备列表
     *
     * @param broadbandNO 宽带账号
     * @return 设备列表
     */
    @Override
    public AjaxResult queryDeviceLeaks(String broadbandNO) {

        // 查询用户在线设备
        IUserDevice iUserDevice = new IUserDevice();
        iUserDevice.setIsGateway(1);
        iUserDevice.setBroadbandNO(broadbandNO);
        iUserDevice.setIsOnline(1);
        List<IUserDevice> onlineDevices = userDeviceMapper.selectIUserDeviceList(iUserDevice);

        List<DeviceItem> items = new ArrayList<>();
        for (IUserDevice device : onlineDevices) {
            // 设备漏洞信息
            List<DeviceLeak> leakList = getLeaksFromRedis(device.getDeviceID(), broadbandNO);
            LOGGER.info("设备ID{},宽带号{}，查出的设备漏洞信息为{}",device.getDeviceID(),broadbandNO,new Gson().toJson(leakList));
            if (leakList == null || leakList.isEmpty()) {
                continue;
            }
            DeviceItem item = new DeviceItem();
            item.setDeviceId(device.getDeviceID());
            item.setDeviceName(device.getDeviceName());
            item.setIconUrl(getDeviceIcon(device.getDeviceName()));
            item.setLeaks(convertToRisk(leakList));
            items.add(item);
        }
        return AjaxResult.success(items);
    }

    /**
     * 获取网络防护开关
     *
     * @param broadbandNO
     * @param scanID
     * @return
     */
    @Override
    public AjaxResult getNetSwitch(String broadbandNO, String scanID) {
        INetProtectSwitch netSwitch = new INetProtectSwitch();
        // 网关弱密码+风险漏洞扣减分数
        int cutScore = getCutScore(netSwitch, broadbandNO);
        // 获取用户各个开关状态
        INetprotectSwitch switchStatus = switchMapper.selectINetprotectSwitchByBroadbandNO(broadbandNO);
        if (null != switchStatus) {
            netSwitch.setPishingFlag(switchStatus.getPishingFlag());
            netSwitch.setCheatSiteFlag(switchStatus.getCheatSiteFlag());
            netSwitch.setVirusFileFlag(switchStatus.getVirusFileFlag());
            netSwitch.setBreachFlag(switchStatus.getBreachFlag());
            cutScore += switchStatus.getPishingFlag() == 0 ? 5 : 0;
            cutScore += switchStatus.getCheatSiteFlag() == 0 ? 5 : 0;
            cutScore += switchStatus.getVirusFileFlag() == 0 ? 5 : 0;
            cutScore += switchStatus.getBreachFlag() == 0 ? 5 : 0;
        } else {
            // 每发现一个防火墙开关未打开扣5分
            cutScore += 20;
        }
        int score = 100 - cutScore > 0 ? 100 - cutScore : 0;
        // 记录得分
        IUserCheckRecord iUserCheckRecord = new IUserCheckRecord();
        iUserCheckRecord.setScanID(scanID);
        iUserCheckRecord.setScore((long) score);
        checkRecordMapper.updateIUserCheckRecord(iUserCheckRecord);
        netSwitch.setScore(score);
        return AjaxResult.success(netSwitch);
    }


    /**
     * <p>
     * 用来生成调用奇安信扫描接口时的scanId参数，scanId要求一段时间内不重复。
     * </p>
     * <p>
     * scanId以001为开头（rapidscan 需要额外指定扫描ID，005开头），共13位，剩下10位数字使用随机+顺序的方式来避免短时间内重复。
     * 本方生成策略为使用ThreadLocalRandom来生成0～9999999(inclusive)之间的随机数，然后再拼接上1～999之间的数值。
     * ThreadLocalRandom比全局的Random类具有更好的性能(less overhead)，以及并发环境下更低的竞争。
     * AtomicInteger可以保证连续999个请求不会重复。
     * </p>
     * <note> 如果此方法不满足需求时，可以适当将中间的随机数范围缩小，后面的有序数值范围增大。
     * 如果还是不行的还，那只能考虑使用类似SnowFlake的生成策略，只是这么短的ID位数不一定可行。 </note>
     *
     * @param scanType
     * @return 以001/005开头的13位整形数值字符串
     */
    public String generateScanId(String scanType) {
        int mid = new SecureRandom().nextInt(999999);
        if (ID_TAIL.get() > 999) {
            ID_TAIL.compareAndSet(1000, 1);
        }
        String scanIdPrefix = "001";
        // rapidscan 需要额外指定扫描ID，005开头
        if ("rapidscan".equals(scanType)) {
            scanIdPrefix = "005";
        }
        return scanIdPrefix + StringUtils.leftPad(mid + "", 7, '0')
                + StringUtils.leftPad(ID_TAIL.getAndIncrement() + "", 3, '0');
    }

    /**
     * 保存用户检测记录
     *
     * @param scanID
     * @param broadbandNO
     */
    private void saveCheckRecord(String scanID, String broadbandNO) {
        if (null != checkRecordMapper.selectIUserCheckRecordByScanID(scanID)) {
            return;
        }
        // 保存扫描记录
        IUserCheckRecord checkRecord = new IUserCheckRecord();
        Date now = TimeTools.getNow();
        checkRecord.setScanID(scanID);
        checkRecord.setBroadbandNO(broadbandNO);
        checkRecord.setCreateTime(now);
        checkRecord.setCheckPwdState(0L);
        checkRecord.setTopolState(0l);
        checkRecord.setStartTime(now);
        checkRecordMapper.insertIUserCheckRecord(checkRecord);
    }

    /**
     * 获取单个设备的漏洞信息
     *
     * @return
     */
    private List<DeviceRisk> convertToRisk(List<DeviceLeak> leakList) {
        List<DeviceRisk> deviceRiskList = new ArrayList<>();
        leakList.forEach(leak -> {
            DeviceRisk risk = new DeviceRisk();
            risk.setLeakType(leak.getLeakType());
            risk.setPort(leak.getPort());
            risk.setServiceName(leak.getServiceName());
            deviceRiskList.add(risk);
        });
        return deviceRiskList;
    }


    /**
     * 用户设备查询
     *
     * @param broadbandNO
     * @param gatewayMac
     */
    private void getDeviceByTopol(String broadbandNO, String gatewayMac, String scanID) {

        try {
            // 家庭拓扑
            NetTopologicalInfo netTopologicalInfo = abilityClient.getTopologicalInfo(gatewayMac);
            LOGGER.info("用户[{}]查询网关mac[{}]拓扑数据{}", broadbandNO, gatewayMac,new Gson().toJson(netTopologicalInfo));

            if (netTopologicalInfo == null || !"0".equals(netTopologicalInfo.getStatus())) {
                LOGGER.info("用户[{}]查询网关mac[{}]拓扑数据出错", broadbandNO, gatewayMac);
                return;
            }

            String lanIP = "";
            GwInfo gwInfo = netTopologicalInfo.getGwInfo();
            if (null != gwInfo) {
                lanIP = gwInfo.getLanIp();
            }

            //先将topoInfo 转换成str，再转成list，否则直接转会报转换异常
            String topoInfoStr = gson.toJson(netTopologicalInfo.getTopoInfo());
            List<NetTopInfo> topoInfo = gson.fromJson(topoInfoStr, new TypeToken<List<NetTopInfo>>(){}.getType());
            //处理路由器信息
            Set<String> apMacSet = dealApInfo(gatewayMac, topoInfo);
            for (NetTopInfo topo : topoInfo) {
                updateUserDevice(broadbandNO, lanIP, topo, apMacSet);
            }
        } catch (Exception e) {
            LOGGER.error("查询用户[{}]网关mac[{}]的家庭网络拓扑数据发生异常,", broadbandNO, gatewayMac, e);
        } finally {
                // 修改扫描记录拓扑状态为已完成
                IUserCheckRecord iUserCheckRecord = new IUserCheckRecord();
                iUserCheckRecord.setScanID(scanID);
                iUserCheckRecord.setTopolState(Long.valueOf(NumberEnum.NUM_INT_2));
                checkRecordMapper.updateIUserCheckRecord(iUserCheckRecord);
        }
    }

    public List<DeviceLeak> getLeaksFromRedis(Long deviceID, String broadbandNO) {
        String json = (String) redisTemplate.opsForHash().get(RedisKeyPrefix.DEVICE_LEAK + deviceID,
                broadbandNO);
        LOGGER.info("从缓存中获取的信息为{}",json);
        if (StringUtils.isNotBlank(json)){
            DeviceLeak[] leaks=new Gson().fromJson(json,DeviceLeak[].class);
            return Arrays.asList(leaks);
        }
     return null;
    }

    public static void main(String[] args) {
        String json = "[{\"leakType\":1,\"port\":0,\"serviceName\":\"\"},{\"leakType\":2,\"port\":80,\"serviceName\":\"网页服务\"}]";
        DeviceLeak[] userList=new Gson().fromJson(json,DeviceLeak[].class);
        System.out.println(new Gson().toJson(Arrays.asList(userList).get(0)));

    }

    /**
     * 更新漏洞信息,放入网关弱密码
     *
     * @param device
     */
    private void updateLeakInfos(IUserDevice device) {
        Long deviceID = device.getDeviceID();
        String broadbandNO = device.getBroadbandNO();
        try {
            String key = RedisKeyPrefix.DEVICE_LEAK + deviceID;
            List<DeviceLeak> leakList = new ArrayList<>();
            Date nowTime = TimeTools.getNow();
            DeviceLeak leak = new DeviceLeak();
            leak.setDeviceID(deviceID);
            leak.setReciveTime(nowTime);
            leak.setLeakType(NumberEnum.NUM_INT_1);
            leakList.add(leak);
            LOGGER.info("新增用户{}网关弱密码漏洞信息,设备:{}", broadbandNO, device.getDeviceMac());
            // 设备漏洞信息存入redis
            redisTemplate.opsForHash().put(key, broadbandNO, gson.toJson(leakList));
            // 设置设备漏洞信息的过期时间为7天
            redisTemplate.expire(key, 7, TimeUnit.DAYS);
        } catch (Exception e) {
            LOGGER.error("新增用户{}网关弱密码漏洞信息,异常", broadbandNO, e);
        }
    }


    /**
     * 用户周报数据汇总
     *
     * @param broadbandNO
     * @param gatewayDevice
     */
    public void reportData(String broadbandNO, IUserDevice gatewayDevice) {

        // 网关信息放入周报设备列表中
        ReportModelData deviceReport = new ReportModelData();
        Set<String> deviceIds = new HashSet<>();
        deviceIds.add(String.valueOf(gatewayDevice.getDeviceID()));
        deviceReport.setBroadbandNO(broadbandNO);
        deviceReport.setDeviceIds(deviceIds);
        // 设备
        deviceReport.setType(NumberEnum.NUM_INT_0);

        try {
            String key = getWeekKey(deviceReport);
            ReportRedisData redisData = getDatas(key);
            if (redisData == null) {
                redisData = new ReportRedisData();
                upateRedisData(redisData, deviceReport);
                redisTemplate.opsForValue().set(key, gson.toJson(redisData), 15, TimeUnit.DAYS);
            } else {
                upateRedisData(redisData, deviceReport);
                // 更新redis中的周报数据，并且不修改原有的过期时间
                redisTemplate.opsForValue().set(key, gson.toJson(redisData), 0);
            }
            recordCheck(deviceReport);
        } catch (Exception e) {
            LOGGER.error("周报数据处理失败", deviceReport.getBroadbandNO(), e);
        }
    }

    /**
     * 获取redis中保存的key
     *
     * @param data
     * @return
     */
    private String getWeekKey(ReportModelData data) {
        LocalDateTime dataTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(data.getTime()), ZoneId.systemDefault());
        LocalDateTime monday = dataTime.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
        // 数据日期所在周的周一日期
        String billDate = monday.format(DTF);
        return RedisKeyPrefix.USER_REPORT_DATA + billDate + ":" + data.getBroadbandNO();
    }


    /**
     * 从redis中获取缓存周报数据
     *
     * @param key
     * @return
     */
    private ReportRedisData getDatas(String key) {
        String json = redisTemplate.opsForValue().get(key);
        if (StringUtils.isNotBlank(json)) {
            return gson.fromJson(json, ReportRedisData.class);
        }
        return null;
    }

    /**
     * 根据设备名称获取设备图标
     * @param deviceName
     * @return
     */
    public String getDeviceIcon(String deviceName) {
        IDeviceIcon iDeviceIcon = new IDeviceIcon();
        iDeviceIcon.setName(deviceName);
        List<IDeviceIcon> iconList = iconMapper.selectIDeviceIconList(iDeviceIcon);
        if (iconList.isEmpty()) {
            IDeviceIcon deviceIcon = new IDeviceIcon();
            deviceIcon.setName("通用设备");
            iconList = iconMapper.selectIDeviceIconList(deviceIcon);
        }
        return iconList.get(0).getIcon();
    }

    /**
     * 周报数据更新
     *
     * @param pushData
     */
    private void upateRedisData(ReportRedisData redisData, ReportModelData pushData) {
        if (StringUtils.isEmpty(pushData.getBroadbandNO())) {
            return;
        }
        Set<String> deviceIDs = pushData.getDeviceIds();
        switch (pushData.getType()) {
            // 扫描次数
            case 0:
                redisData.setTime(redisData.getScanTime() + 1);
                redisData.getDevices().addAll(deviceIDs);
                break;
            // 设备漏洞
            case 1:
                redisData.getLeaks().addAll(deviceIDs);
                break;
            // 网关弱密码
            case 2:
                redisData.getWeekpwd().addAll(deviceIDs);
                break;
            // 摄像头异常
            case 3:
                redisData.getAbnormal().addAll(deviceIDs);
                redisData.getAbnormalDetail().add(pushData.getTime());
                break;
            // 失陷设备
            case 4:
                redisData.getFallDevice().addAll(deviceIDs);
                break;
            // 摄像头设备
            case 5:
                redisData.getCamera().addAll(deviceIDs);
                break;
            default:
                break;
        }
    }

    private Set<String> dealApInfo(String gatewayMac, List<NetTopInfo> topoInfo) {
        String ap_key = RedisKeyPrefix.AP_INFO + gatewayMac;
        redisTemplate.opsForHash().delete(ap_key, gatewayMac);
        List<ApInfoData> apInfoList = new ArrayList<>();
        Set<String> apMacSet = new HashSet<>();
        //获取所有路由器的信息并存入Redis中
        for (NetTopInfo topo : topoInfo) {
            dealRedisApInfo(gatewayMac, topo, apInfoList);
        }
        redisTemplate.opsForHash().put(ap_key, gatewayMac, gson.toJson(apInfoList));
        redisTemplate.expire(ap_key, 1, TimeUnit.DAYS);
        //获取桥接模式且是elink的路由器mac
        for (NetTopInfo topo : topoInfo) {
            if ("elink".equals(topo.getDevType()) && "bridge".equals(topo.getWorkmode())) {
                apMacSet.add(topo.getMac());
            }
        }
        LOGGER.info("网关[{}]下桥接模式的路由器Mac有[{}]", gatewayMac, gson.toJson(apMacSet));
        return apMacSet;
    }

    /**
     * 获取所有路由器的信息并存入Redis中
     * @param gatewayMac
     * @param topo
     * @param apInfoList
     */
    private void dealRedisApInfo(String gatewayMac, NetTopInfo topo, List<ApInfoData> apInfoList){
        try{
            String devType = topo.getDevType();
            if(StringUtils.isEmpty(devType) || !devType.equals("elink")){
                return;
            }
            GetApInfo getApInfo = abilityClient.getApInfo(topo.getMac());
            if(getApInfo == null || !"3.0".equals(getApInfo.getVersion())){
                return;
            }
            ApInfoData apInfoData = new ApInfoData();
            String devName = StringUtils.isEmpty(topo.getDevname()) ? topo.getModel() : topo.getDevname();
            apInfoData.setDeviceMac(topo.getMac());
            apInfoData.setDeviceName(devName);
            apInfoData.setWorkmode(topo.getWorkmode());
            apInfoList.add(apInfoData);
        }catch (Exception e){
            LOGGER.error("网关[{}]更新Redis中路由器信息出错,",gatewayMac,e);
        }
    }

    /**
     * 设备信息处理
     *
     * @param broadbandNO
     * @param lanIP       网关lanIP
     * @param topo
     * @param apMacSet    路由器mac集合
     */
    private void updateUserDevice(String broadbandNO, String lanIP, NetTopInfo topo, Set<String> apMacSet) {
        String deviceName = topo.getDevname();
        int isOnline = getOnlineFlag(lanIP, topo, apMacSet);
        if (isOnline == NumberEnum.NUM_INT_0) {
            LOGGER.info("设备[{}]不在线，不进行处理", topo.getMac());
            return;
        }
        // 拓扑接口中在线的新设备 ，记录它的首次上线时间
        dealFirstTime(broadbandNO, topo.getMac(), deviceName, topo.getLastActiveTime());
        //处理设备信息
        dealUserDevice(broadbandNO, deviceName, isOnline, topo);
    }

    /**
     * 判断用户设备是否在线
     *
     * @param lanIP
     * @param topo
     * @param apMacSet 路由器mac集合
     * @return
     */
    private int getOnlineFlag(String lanIP, NetTopInfo topo, Set<String> apMacSet) {
        if (topo.getActive() == 0) {
            return NumberEnum.NUM_INT_0;
        }
        if (StringUtils.isEmpty(topo.getMac()) || StringUtils.isEmpty(topo.getIp())) {
            return NumberEnum.NUM_INT_0;
        }
        String parMac = topo.getParMac();
        if (!StringUtils.isEmpty(parMac) && apMacSet.contains(parMac)) {
            return NumberEnum.NUM_INT_1;
        }
        String deviceIP = topo.getIp();
        String prefixLanIP = lanIP.substring(0, lanIP.lastIndexOf("."));
        if (!deviceIP.startsWith(prefixLanIP)) {
            return NumberEnum.NUM_INT_0;
        }
        return NumberEnum.NUM_INT_1;
    }

    /**
     * 记录新设备首次上线时间
     * @param broadbandNO
     * @param deviceMac
     * @param deviceType
     *
     */
    private void dealFirstTime(String broadbandNO,String deviceMac,String deviceType,String lastActiveTime) {
        try{
            IDeviceGoonline goonline = new IDeviceGoonline();
            goonline.setBroadbandNO(broadbandNO);

            goonline.setReceiveTime(TimeTools.getDateTime(lastActiveTime,"yyyy-MM-dd HH:mm:ss"));
            goonline.setDeviceMac(deviceMac);
            goonline.setShowName(deviceType);
            int count = goonlineMapper.insertIDeviceGoonline(goonline);
            LOGGER.info("用户[{}]新增上线设备,设备mac为[{}],结果为[{}]", broadbandNO, deviceMac,count);
        }catch (Exception e){
            LOGGER.error("用户[{}]新增上线设备,设备mac为[{}]出错,",broadbandNO,deviceMac,e);
        }
    }

    /**
     * 处理设备信息
     * @param broadbandNO
     * @param deviceName
     * @param isOnline
     * @param topo
     */
    private void dealUserDevice(String broadbandNO, String deviceName, int isOnline, NetTopInfo topo){
        try{
            // 新增or更新在线设备
            IUserDevice userDevice = deviceMapper.selectIUserDeviceByMac(broadbandNO, topo.getMac());
            if (userDevice == null) {
                LOGGER.info("用户[{}]新增设备[{}-{}]", broadbandNO, topo.getMac(), deviceName);
                userDevice = new IUserDevice();
                addNewDevice(topo,userDevice,broadbandNO,deviceName,isOnline);
                // 发现设备名称中包含音箱和摄像的设备上报迈科并且安装迈科插件
                reportAndPlugin(userDevice, deviceName,topo);
            } else {
                LOGGER.info("用户[{}]更新设备[{}-{}]", broadbandNO, topo.getMac(), topo.getDevname());
                userDevice.setDeviceName(deviceName);
                userDevice.setShowName(deviceName);
                userDevice.setNameSource(NameSource.WG.getType());
                userDevice.setIsOnline(isOnline);
                userDevice.setIsGateway(FlagType.NO.getVal());
                userDevice.setReceiveTime(new Date());
                deviceMapper.updateIUserDevice(userDevice);
                // 同步更新流量设备表中设备名称
                IFlowDevice iFlowDevice = new IFlowDevice();
                iFlowDevice.setDeviceName(deviceName);
                iFlowDevice.setDeviceId(userDevice.getDeviceID());
                flowDeviceMapper.updateIFlowDevice(iFlowDevice);
            }
        }catch (Exception e){
            LOGGER.error("处理设备信息出错,",e);
        }
    }

    /**
     * 插入新设备
     * @param topo
     * @param userDevice
     * @param broadbandNO
     * @param deviceName
     * @param isOnline
     */
    private void addNewDevice(NetTopInfo topo, IUserDevice userDevice,
                              String broadbandNO, String deviceName, int isOnline){
        userDevice.setBroadbandNO(broadbandNO);
        userDevice.setDeviceMac(topo.getMac());
        userDevice.setDeviceName(deviceName);
        userDevice.setShowName(deviceName);
        userDevice.setNameSource(NameSource.WG.getType());
        userDevice.setIpv4add(topo.getIp());
        userDevice.setIsOnline(isOnline);
        userDevice.setIsGateway(FlagType.NO.getVal());
        userDevice.setReceiveTime(new Date());
        deviceMapper.insertIUserDevice(userDevice);
    }

    /**
     * 更新终端检测记录
     *
     * @param pushData
     */
    private void recordCheck(ReportModelData pushData) {
        try {
            LocalDateTime dataTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(pushData.getTime()), ZoneId.systemDefault());
            // 数据日期
            String sdate = dataTime.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
            int deviceNum = 0;
            int fallDeviceNum = 0;
            int riskNum = 0;
            int type = pushData.getType();
            int deviceCount = pushData.getDeviceIds().size();
            // 不同类型代表不同的统计
            if(type == 0) {
                deviceNum = deviceCount;
            } else if(type == 1) {
                riskNum = deviceCount;
            } else if(type == 2) {
                riskNum = deviceCount;
            } else if (type == 4) {
                fallDeviceNum = deviceCount;
            }
            IScanStat oldStat = statMapper.selectIScanStatBySdate(sdate);
            if (oldStat == null) {
                IScanStat stat = new IScanStat();
                stat.setSdate(sdate);
                stat.setDeviceNum(deviceNum);
                stat.setFallDeviceNum(fallDeviceNum);
                stat.setRiskNum(riskNum);
                statMapper.insertIScanStat(stat);
            } else {
                oldStat.setDeviceNum(oldStat.getDeviceNum() + deviceNum);
                oldStat.setFallDeviceNum(oldStat.getFallDeviceNum() + fallDeviceNum);
                oldStat.setRiskNum(oldStat.getRiskNum() + riskNum);
                statMapper.updateIScanStat(oldStat);
            }
        } catch (Exception e) {
           LOGGER.error("统计终端检测信息异常{}", e);
        }
    }

    /**
     * 设备名称中包含摄像和音箱，门铃的设备上报迈科，天翼摄像头tycamera、普通摄像头camera、音箱等其他设备other
     *
     * @param userDevice
     * @param deviceName
     */
    private void reportAndPlugin(IUserDevice userDevice, String deviceName, NetTopInfo topo) {
        try{
            String type = mkReport.getType(userDevice);
            if(StringUtils.isEmpty(type)){
                return;
            }
            LOGGER.info("deviceID{{}],deviceName{{}},deviceMac[{}],上报迈科", userDevice.getDeviceID(),
                    deviceName, userDevice.getDeviceMac());
            mkReport.mkReport(userDevice.getDeviceMac(), type, "add");
            //记录到用户监测设备表I_FLOW_DEVICE

            List<IFlowDevice> flowDeviceList = new ArrayList<>();
            if(userDevice.getDeviceID() != null){
                IFlowDevice flowDevice = new IFlowDevice();
                flowDevice.setDeviceId(userDevice.getDeviceID());
                flowDeviceList = flowDeviceMapper.selectIFlowDeviceList(flowDevice);
            }

            if(flowDeviceList.isEmpty()){
                IFlowDevice info = new IFlowDevice();
                String deviceShowName = userDevice.getShowName();
                info.setBroadbandNO(userDevice.getBroadbandNO());
                info.setDeviceId(userDevice.getDeviceID());
                info.setDeviceName(deviceShowName);
                info.setCreateTime(TimeTools.getNow());
                info.setDeviceType(getDeviceType(deviceShowName));
                info.setDeviceMac(userDevice.getDeviceMac());
                flowDeviceMapper.insertIFlowDevice(info);
            }
        }catch (Exception e){
            LOGGER.error("更新流量监测设备信息出错,",e);
        }
    }

    /**
     * 计算用户此次立即扫描得分
     *
     * @parma netSwitch
     * @param broadbandNO
     * @return
     */
    private int getCutScore(INetProtectSwitch netSwitch, String broadbandNO) {
        int cutScore = 0;
        // 查询用户在线设备
        IUserDevice iUserDevice = new IUserDevice();
        iUserDevice.setBroadbandNO(broadbandNO);
        List<IUserDevice> onlineDevices = userDeviceMapper.selectIUserDeviceList(iUserDevice);
        // 所有设备漏洞信息
        for (IUserDevice device : onlineDevices) {
            List<DeviceLeak> leakList = getLeaksFromRedis(device.getDeviceID(),
                    broadbandNO);
            if (leakList == null || leakList.isEmpty()) {
                continue;
            }
            // WiFi弱密码扣10分，每存在风险开放端口的设备扣5分
            if (device.getIsGateway() == FlagType.YES.getVal()) {
                cutScore += 10;
            } else {
                cutScore += 5;
            }
        }
        return cutScore;
    }
}
